using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;
using System.Threading;
using StupidSpaceThing.MenuSystem;

namespace StupidSpaceThing
{
	internal static class Bullets
	{
		private static LinkedList<Bullet> bullets = new LinkedList<Bullet>();
		public static int BulletCount { get { return bullets.Count; } }
		private static LinkedList<Bullet> bulletsHitTest = new LinkedList<Bullet>();		
		private static GraphicsDevice device;
		private static Effect effect;
		private static EffectParameter worldParameter;
		private static EffectParameter viewParameter;
		private static EffectParameter projectionParameter;
		private static VertexPositionColor[] spriteArray;
		private static VertexDeclaration vertexDeclaration;
		private static int maxBullets = 0;
		
		private static Stack<Bullet> bulletPool;
		public static int BulletPool { get { return bulletPool.Count; } }

		private static volatile Thread thread;

		public static void Stop() {
			if( thread != null ) {
				thread.Abort();
			}
			thread = null;
		}

		public static void Init(GraphicsDevice _device) {
			device = _device;
			vertexDeclaration = new VertexDeclaration(device, VertexPositionColor.VertexElements);

			if( bulletPool == null ) {
				bulletPool = new Stack<Bullet>();
				expandPool(1000);
			}

			if( thread != null ) {
				thread.Abort();				
			}
			thread = new Thread(new ThreadStart(hitTest));
			thread.Start();
		}
		public static void Init(Effect _effect, Texture2D texture) {
			effect = _effect;
			worldParameter = _effect.Parameters["World"];
			viewParameter = _effect.Parameters["View"];
			projectionParameter = _effect.Parameters["Projection"];
            effect.Parameters["SpriteTexture"].SetValue(texture);
			//colorParameter = _effect.Parameters["Color"];
		}

		private static void expandPool(int count) {
			maxBullets += count;
			for( int i = 0; i < count; i++ ) {
				bulletPool.Push(new Bullet());
			}
			spriteArray = new VertexPositionColor[maxBullets * 3];
		}

		public static Bullet GetBullet(Color color, FactionEntity owner, Vector3 direction, float yModifier, RealGameTime gameTime, bool hitTest){
			return GetBullet(color, owner, direction, new Vector3(0, yModifier, 0), gameTime, hitTest);
		}

		public static Bullet GetBullet(Color color, FactionEntity owner, Vector3 direction, Vector3 positionModifier, RealGameTime gameTime, bool hitTest) {
			if( bulletPool.Count == 0 ) {
				expandPool(1000);
			}
			Bullet bullet = bulletPool.Pop();
			bullet.Color = color;
			bullet.Owner = owner;
			bullet.Direction = direction;
			bullet.Direction.Normalize();
			bullet.Direction += Bullet.Jitter();
			bullet.Direction.Normalize();
			bullet.RotationMatrix = Matrix.CreateRotationY((float)Math.Atan2(bullet.Direction.X, bullet.Direction.Z));
			bullet.Position = owner.Position + positionModifier;
			if( gameTime != null ) {
				bullet.Fired = gameTime.TotalRealGameTime.TotalMilliseconds;
			} else {
				bullet.Fired = 0;
			}
			bullet.HitTest = hitTest;
			bullet.Dead = false;
			bullet.Intensity = 1;
			return bullet;
		}

		public static Vector3 FirePredict(RealGameTime gameTime, FactionEntity owner, FactionEntity target) {
			if( target.Entity.ColObject == null ) {
				return target.Position;
			}

			float projectileSpeed = Bullet.Speed;
			Vector2 enemyPos = target.Position2D;
			Vector2 shootPos = owner.Position2D;
			Vector2 diff = enemyPos - shootPos;
			float a = -projectileSpeed * projectileSpeed;
			float b = 0.0f;
			float c = 0.0f;
			float target_xSpeed = target.Entity.ColObject.Body.LinearVelocity.X * 0.02f;
			float target_ySpeed = target.Entity.ColObject.Body.LinearVelocity.Y * 0.02f;
			a += target_xSpeed * target_xSpeed;
			a += target_ySpeed * target_ySpeed;
			b += diff.X * target_xSpeed;
			b += diff.Y * target_ySpeed;
			c += diff.X * diff.X;
			c += diff.Y * diff.Y;
			b *= 2.0f;
			float t = (float)(-b - Math.Sqrt(b * b - 4 * a * c)) / (2 * a);
			Vector2 res = new Vector2(enemyPos.X + target_xSpeed * t, enemyPos.Y + target_ySpeed * t);

			float angle = (float)Math.Atan2(res.Y - shootPos.Y, res.X - shootPos.X);

			float bullet_xSpeed = (float)Math.Cos(angle) * projectileSpeed;
			float bullet_ySpeed = (float)Math.Sin(angle) * projectileSpeed;
			//Bullets.Fire(new Bullet(owner.Faction.Color, owner, new Vector3(bullet_xSpeed, 0, bullet_ySpeed), yModifier, gameTime, true));
			return new Vector3(bullet_xSpeed, 0, bullet_ySpeed);
		}

		public static void Fire(Color color, FactionEntity owner, Vector3 direction, float yModifier, RealGameTime gameTime) {
			Fire(GetBullet(color, owner, direction, yModifier, gameTime, true));
		}
		public static void Fire(Color color, FactionEntity owner, FactionEntity target, float yModifier, RealGameTime gameTime) {
			Fire(GetBullet(color, owner, target.Position - owner.Position, yModifier, gameTime, true));
		}
		public static void Fire(Bullet bullet){
			if( thread == null ) return;
			lock( bullets ) {
				bullets.AddLast(bullet);
			}
			if( bullet.HitTest ) {
				lock( thread ) {
					bulletsHitTest.AddLast(bullet);
				}
			}
		}

		public static void Remove(Bullet bullet) {
			if( thread == null ) return;
			bullets.Remove(bullet);
			if( bullet.HitTest ) {
				lock( thread ) {
					bulletsHitTest.Remove(bullet);
				}
			}
		}
		
		private static List<LinkedList<FactionEntity>> hitEntities = new List<LinkedList<FactionEntity>>();
		public static void HitTest(List<LinkedList<FactionEntity>> entities) {
			if( thread == null ) return;
			lock( thread ){
				hitEntities = entities;
			}
		}

		public static void hitTest() {
			#if XBOX
			thread.SetProcessorAffinity(4);
			#endif

			while( true ) {
				lock( thread ) {					
					foreach( Bullet bullet in bulletsHitTest ) {
						if( bullet.Intensity == 0 ) continue;
						foreach( LinkedList<FactionEntity> hitList in hitEntities ) {
							LinkedListNode<FactionEntity> feNode = hitList.First;
							while( feNode != null ){
								FactionEntity fe = feNode.Value;
								if( fe == bullet.Owner || fe.Entity.Dead ) {
									feNode = feNode.Next;
									continue;
								}								
								ColObject colObject = fe.Entity.ColObject;
								if( colObject != null && colObject.Within(bullet.Position2D) ) {
									if( fe is Unit ) {
										(fe as Unit).ApplyForce(bullet.Direction2D * 200, true);
									}
									bullet.Die();
									bullet.Intensity = 0;
									if( fe.Faction != bullet.Owner.Faction ) { // no friendly fire
										fe.Entity.ApplyDamage(10, bullet.Owner);
										bullet.Owner.Target = fe;
									}
								}
								feNode = feNode.Next;
							}
						}
					}					
				}
				Thread.Sleep(30);
			}
		}

		public static void Update(RealGameTime gameTime) {
			LinkedListNode<Bullet> curBullet = bullets.First;
			while( curBullet != null ){
				Bullet bullet = curBullet.Value;
				if( bullet.Fired == 0 ) bullet.Fired = gameTime.TotalRealGameTime.TotalMilliseconds;
				if( gameTime.TotalRealGameTime.TotalMilliseconds - Bullet.LifeSpan > bullet.Fired ) {
					bullet.Die();
				}
				if( bullet.Intensity == 0 ) {
					LinkedListNode<Bullet> nextBullet = curBullet.Next;
					Remove(bullet);
					bulletPool.Push(bullet);
					curBullet = nextBullet;
				} else {
					bullet.Update(gameTime);
					curBullet = curBullet.Next;
				}
			}			
		}

		public static void Draw(Camera camera) {
            if (bullets.Count == 0)
                return;
			viewParameter.SetValue(camera.View);
			projectionParameter.SetValue(camera.Projection);
            worldParameter.SetValue(Matrix.Identity);
			device.VertexDeclaration = vertexDeclaration;
            device.RenderState.PointSpriteEnable = true;
            device.RenderState.PointSize = 16f;
            device.RenderState.AlphaBlendEnable = true;
            device.RenderState.SourceBlend = Blend.SourceAlpha;
            device.RenderState.DestinationBlend = Blend.One;
            device.RenderState.DepthBufferWriteEnable = false;

			int i = 0;
			lock( bullets ) {								
				foreach( Bullet bullet in bullets ) {					
					Vector4 tempVector = bullet.Color.ToVector4();
					tempVector.W = bullet.Intensity;
					Color tempColor = new Color(tempVector);
					spriteArray[i + 0].Position = bullet.Position + 0.50f * bullet.Direction;
					spriteArray[i + 0].Color = tempColor;
					spriteArray[i + 1].Position = bullet.Position - 0.50f * bullet.Direction;
					spriteArray[i + 1].Color = tempColor;
					spriteArray[i + 2].Position = bullet.Position;
					spriteArray[i + 2].Color = tempColor;
					/*spriteArray[i + 3].Position = bullet.Position - 0.25f * bullet.Direction;
					spriteArray[i + 3].Color = tempColor;
					spriteArray[i + 4].Position = bullet.Position + 0.25f * bullet.Direction;
					spriteArray[i + 4].Color = tempColor;*/
					i += 3;
				}
			}

			int toDraw = i - 3;
			if( toDraw > 0 ) {
				effect.Begin();
				foreach( EffectPass pass in effect.CurrentTechnique.Passes ) {
					pass.Begin();
					device.DrawUserPrimitives<VertexPositionColor>(PrimitiveType.PointList, spriteArray, 0, toDraw);
					pass.End();
				}
				effect.End();
			}

            device.RenderState.PointSpriteEnable = false;
            device.RenderState.DepthBufferWriteEnable = true;
            device.RenderState.SourceBlend = Blend.SourceAlpha;
            device.RenderState.DestinationBlend = Blend.InverseSourceAlpha;
		}  
	}
		
	internal class Bullet
	{
		public Color Color;
		public FactionEntity Owner;
		public Vector3 Direction;
		public Vector2 Direction2D { get { return new Vector2(Direction.X, Direction.Z); } }
		public Vector3 Position;		
		public Vector2 Position2D { get { return new Vector2(Position.X, Position.Z); } }
		public double Fired = 0;
		public Matrix RotationMatrix;
		public bool Dead = false;		
		public const float Speed = 1;
		public const int LifeSpan = 400;
		public float Intensity = 1;
		public bool HitTest;

		private static int jitterCount = 0;
		private static float[] jitterNumbers = null;
		private const int jitterTotal = 300;
		private const float jitter = 0.1f;

		public Bullet() { }
				
		static Bullet() {
			if( jitterNumbers == null ) {
				jitterNumbers = new float[jitterTotal * 3];
				for( int i = 0; i < jitterTotal; i++ ) {
					jitterNumbers[i] = (float)TopMenu.Rand.NextDouble() * jitter - (jitter / 2);
					jitterNumbers[i + 1] = (float)TopMenu.Rand.NextDouble() * jitter - (jitter / 2);
					jitterNumbers[i + 2] = (float)TopMenu.Rand.NextDouble() * jitter - (jitter / 2);
				}
			}
		}

		public static Vector3 Jitter() {
			Vector3 jitterDir = new Vector3(jitterNumbers[jitterCount], jitterNumbers[jitterCount + 1], jitterNumbers[jitterCount + 2]);
			jitterCount++;
			if( jitterCount == jitterTotal ) {
				jitterCount = 0;
			}
			return jitterDir;
			
		}

		public void Update(RealGameTime gameTime) {
			Position += Direction * Speed;
			if( Dead ) {
				Intensity -= 0.05f;
				if( Intensity < 0 ) {
					Intensity = 0;
				}
			}
		}

		public void Die() {
			Dead = true;
		}
	}
}
